mod cli;

use std::{hash::Hasher, net::SocketAddr, sync::Arc};

use axum::{Extension, Router};
use casbin::MgmtApi;
use clap::Parser;
use cli::{InitArgs, RunArgs};
use fred::{clients::RedisPool, interfaces::ClientLike as _, types::RedisConfig};
use loginmanager::CurrentUser;
use sea_orm::{Database, DatabaseConnection};
use tower_http::trace::TraceLayer;
use tower_sessions::{cookie::time::Duration, SessionManagerLayer};
use web::auth_manager::{AuthManager, LoginUser};
use web::controller;
use web::middleware::{casbin_middleware, session_add_middleware};
use web::service::cache_service::{ICacheService, MokaCache, RedisCache};
use web::state::AppState;

#[tokio::main]
async fn main() {
    dotenv::dotenv().ok();
    if std::env::var_os("RUST_LOG").is_none() {
        std::env::set_var("RUST_LOG", "tower_http=info,middleware=info");
    }
    let args = crate::cli::WebCli::parse();
    // 默认运行run命令
    match args.command {
        Some(cli::Commands::Run(args)) => run(args).await,
        Some(cli::Commands::Init(args)) => init(args).await,
        None => run(cli::RunArgs::default()).await,
    }
}

async fn run(args: RunArgs) {
    web::print_banner();
    let cnf = load_config(&args.config_file);
    // 初始化日志系统
    let _guard = cnf.logger.init_log();

    // 初始化数据库
    let conn: DatabaseConnection = Database::connect(cnf.database.get_connect_opt())
        .await
        .unwrap();

    let (tera, tera_context) = cnf.template.get_tera();

    let config = web::controller::auth::JwtConfigBuilder::from_file().builder();

    // 初始化casbin
    let mut enforcer = cnf
        .casbin
        .expect("miss casbin config")
        .casbin()
        .await
        .unwrap();
    let policies = web::utils::casbin_policies(&conn, None, None)
        .await
        .unwrap();
    enforcer.add_policies(policies).await.unwrap();

    let redis_config = cnf.redis.and_then(|f| RedisConfig::from_url(&f.url()).ok());
    let pool = if let Some(c) = redis_config {
        let pool = RedisPool::new(c, None, None, None, 4).unwrap();
        let _ = pool.connect();
        pool.wait_for_connect().await.unwrap();
        Some(pool)
    } else {
        None
    };

    let mut auth_manager = AuthManager::new(conn.clone());
    let cache: Box<dyn ICacheService> = if let Some(pool) = pool.clone() {
        auth_manager.set_redis(pool.clone());
        Box::new(RedisCache::new(pool))
    } else {
        Box::new(MokaCache::new(1024))
    };

    let share_state = Arc::new(AppState::new(
        conn,
        tera,
        tera_context,
        cache,
        enforcer,
        None,
    ));

    let session_layer =
        SessionManagerLayer::new(web::middleware::StateStore::new(share_state.clone()))
            .with_secure(cnf.loginmanager.secure)
            .with_path(cnf.loginmanager.path)
            .with_expiry(tower_sessions::Expiry::OnInactivity(Duration::seconds(
                30 * 60,
            )));

    let decoder = web::auth_manager::AneBackend::default();
    let loginmanager = loginmanager::LoginManager::new(decoder)
        .login_view(cnf.loginmanager.login_view)
        .redirect(cnf.loginmanager.redirect);

    let mut app = Router::new()
        .nest("/file", uploader::get_route(None))
        .nest("/api/system", controller::system::get_route())
        .nest("/api/", controller::get_route())
        // ↑↑↑ 以上服务需要通过casbin请求判断
        .route_layer(axum::middleware::from_fn(casbin_middleware))
        .nest("/api/account", controller::account::get_route())
        // ↑↑↑ 以上服务需要用户登陆才能访问
        .route_layer(axum::middleware::from_extractor::<CurrentUser<LoginUser>>())
        // 认证登陆相关的api
        .nest("/auth", controller::auth::get_route())
        .route_layer(axum::middleware::from_fn(session_add_middleware))
        .layer(loginmanager)
        .layer(Extension(config))
        .layer(Extension(auth_manager))
        .layer(session_layer)
        .layer(TraceLayer::new_for_http())
        .layer(Extension(share_state.clone()))
        .with_state(share_state)
        .merge(cnf.web.get_static_route())
        .nest_service("", cnf.web.html_server());
    if let Some(cors) = cnf.web.cors() {
        app = app.layer(cors);
    }

    let tcp_listener = tokio::net::TcpListener::bind(&cnf.web.addr).await.unwrap();
    tracing::info!("listening on http://{}", cnf.web.addr);
    axum::serve(
        tcp_listener,
        app.into_make_service_with_connect_info::<SocketAddr>(),
    )
    .await
    .unwrap();
}

/// 从配置文件加载配置
pub fn load_config(file: &str) -> web_config::Config {
    let cnf =
        web_config::Config::from_file(file).expect(&format!("config file `{file}` not exits."));
    if let Some(author) = &cnf.author {
        let mut hasher = std::hash::DefaultHasher::new();
        hasher.write_u8(b'0');
        hasher.write_u8(b'2');
        hasher.write_u8(b'7');
        let h027 = hasher.finish();
        let mut hasher = std::hash::DefaultHasher::new();
        hasher.write(author.as_bytes());
        if h027 != hasher.finish() {
            None
        } else {
            Some(cnf)
        }
    } else {
        None
    }
    .expect("error author")
}

async fn init(args: InitArgs) {
    let cnf = load_config(&args.config_file);
    let conn: DatabaseConnection = Database::connect(cnf.database.get_connect_opt())
        .await
        .unwrap();
    if args.name == "db" {
        ane_system::Mutation::init_db(&conn).await.unwrap();
        ane_cms::mutation::init_db(&conn).await.unwrap();
    } else if args.name == "super_user" {
        ane_system::Mutation::add_super_user(&conn).await.unwrap();
    }
}
